# frozen_string_literal: true

require "cases/helper_sqlserver"

class ColumnTestSQLServer < ActiveRecord::TestCase
  it "#table_name" do
    assert SSTestDatatype.columns.all? { |c| c.table_name == "sst_datatypes" }
    assert SSTestCustomersView.columns.all? { |c| c.table_name == "customers" }
  end

  describe "ActiveRecord::ConnectionAdapters::SQLServer::Type" do
    let(:obj) { SSTestDatatype.new }

    def new_obj
      SSTestDatatype.new
    end

    def column(name)
      SSTestDatatype.columns_hash[name]
    end

    def assert_obj_set_and_save(attribute, value)
      obj.send :"#{attribute}=", value
      _(obj.send(attribute)).must_equal value
      obj.save!
      _(obj.reload.send(attribute)).must_equal value
    end

    # http://msdn.microsoft.com/en-us/library/ms187752.aspx

    # Exact Numerics

    it "int(4) PRIMARY KEY" do
      col = column("id")
      _(col.sql_type).must_equal "int(4)"
      _(col.null).must_equal false
    end

    it "bigint(8)" do
      col = column("bigint")
      _(col.sql_type).must_equal "bigint(8)"
      _(col.type).must_equal :integer
      _(col.null).must_equal true
      _(col.default).must_equal 42
      _(obj.bigint).must_equal 42
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::BigInteger
      _(type.limit).must_equal 8
      assert_obj_set_and_save :bigint, -9_223_372_036_854_775_808
      assert_obj_set_and_save :bigint, 9_223_372_036_854_775_807
    end

    it "int(4)" do
      col = column("int")
      _(col.sql_type).must_equal "int(4)"
      _(col.type).must_equal :integer
      _(col.null).must_equal true
      _(col.default).must_equal 42
      _(obj.int).must_equal 42
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Integer
      _(type.limit).must_equal 4
      assert_obj_set_and_save :int, -2_147_483_648
      assert_obj_set_and_save :int, 2_147_483_647
    end

    it "smallint(2)" do
      col = column("smallint")
      _(col.sql_type).must_equal "smallint(2)"
      _(col.type).must_equal :integer
      _(col.null).must_equal true
      _(col.default).must_equal 42
      _(obj.smallint).must_equal 42
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::SmallInteger
      _(type.limit).must_equal 2
      assert_obj_set_and_save :smallint, -32_768
      assert_obj_set_and_save :smallint, 32_767
    end

    it "tinyint(1)" do
      col = column("tinyint")
      _(col.sql_type).must_equal "tinyint(1)"
      _(col.type).must_equal :integer
      _(col.null).must_equal true
      _(col.default).must_equal 42
      _(obj.tinyint).must_equal 42
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::TinyInteger
      _(type.limit).must_equal 1
      assert_obj_set_and_save :tinyint, 0
      assert_obj_set_and_save :tinyint, 255
    end

    it "bit" do
      col = column("bit")
      _(col.sql_type).must_equal "bit"
      _(col.type).must_equal :boolean
      _(col.null).must_equal true
      _(col.default).must_equal true
      _(obj.bit).must_equal true
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Boolean
      _(type.limit).must_be_nil
      obj.bit = 0
      _(obj.bit).must_equal false
      obj.save!
      _(obj.reload.bit).must_equal false
      obj.bit = "1"
      _(obj.bit).must_equal true
      obj.save!
      _(obj.reload.bit).must_equal true
    end

    it "decimal(9,2)" do
      col = column("decimal_9_2")
      _(col.sql_type).must_equal "decimal(9,2)"
      _(col.type).must_equal :decimal
      _(col.null).must_equal true
      _(col.default).must_equal BigDecimal("12345.01")
      _(obj.decimal_9_2).must_equal BigDecimal("12345.01")
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Decimal
      _(type.limit).must_be_nil
      _(type.precision).must_equal 9
      _(type.scale).must_equal 2
      obj.decimal_9_2 = "1234567.8901"
      _(obj.decimal_9_2).must_equal BigDecimal("1234567.89")
      obj.save!
      _(obj.reload.decimal_9_2).must_equal BigDecimal("1234567.89")
    end

    it "decimal(16,4)" do
      col = column("decimal_16_4")
      _(col.sql_type).must_equal "decimal(16,4)"
      _(col.default).must_equal BigDecimal("1234567.89")
      _(obj.decimal_16_4).must_equal BigDecimal("1234567.89")
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type.precision).must_equal 16
      _(type.scale).must_equal 4
      obj.decimal_16_4 = "1234567.8901001"
      _(obj.decimal_16_4).must_equal BigDecimal("1234567.8901")
      obj.save!
      _(obj.reload.decimal_16_4).must_equal BigDecimal("1234567.8901")
    end

    it "numeric(18,0)" do
      col = column("numeric_18_0")
      _(col.sql_type).must_equal "numeric(18,0)"
      _(col.type).must_equal :decimal
      _(col.null).must_equal true
      _(col.default).must_equal BigDecimal(191)
      _(obj.numeric_18_0).must_equal BigDecimal(191)
      _(col.default_function).must_be_nil

      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::DecimalWithoutScale
      _(type.limit).must_be_nil
      _(type.precision).must_equal 18
      _(type.scale).must_be_nil

      obj.numeric_18_0 = "192.1"
      _(obj.numeric_18_0).must_equal BigDecimal(192)

      obj.save!
      _(obj.reload.numeric_18_0).must_equal BigDecimal(192)
    end

    it "numeric(36,2)" do
      col = column("numeric_36_2")
      _(col.sql_type).must_equal "numeric(36,2)"
      _(col.type).must_equal :decimal
      _(col.null).must_equal true
      _(col.default).must_equal BigDecimal("12345678901234567890.01")
      _(obj.numeric_36_2).must_equal BigDecimal("12345678901234567890.01")
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Decimal
      _(type.limit).must_be_nil
      _(type.precision).must_equal 36
      _(type.scale).must_equal 2
      obj.numeric_36_2 = "192.123"
      _(obj.numeric_36_2).must_equal BigDecimal("192.12")
      obj.save!
      _(obj.reload.numeric_36_2).must_equal BigDecimal("192.12")
    end

    it "money" do
      col = column("money")
      _(col.sql_type).must_equal "money"
      _(col.type).must_equal :money
      _(col.null).must_equal true
      _(col.default).must_equal BigDecimal("4.20")
      _(obj.money).must_equal BigDecimal("4.20")
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Money
      _(type.limit).must_be_nil
      _(type.precision).must_equal 19
      _(type.scale).must_equal 4
      obj.money = "922337203685477.58061"
      _(obj.money).must_equal BigDecimal("922337203685477.5806")
      obj.save!
      _(obj.reload.money).must_equal BigDecimal("922337203685477.5806")
    end

    it "smallmoney" do
      col = column("smallmoney")
      _(col.sql_type).must_equal "smallmoney"
      _(col.type).must_equal :smallmoney
      _(col.null).must_equal true
      _(col.default).must_equal BigDecimal("4.20")
      _(obj.smallmoney).must_equal BigDecimal("4.20")
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::SmallMoney
      _(type.limit).must_be_nil
      _(type.precision).must_equal 10
      _(type.scale).must_equal 4
      obj.smallmoney = "214748.36461"
      _(obj.smallmoney).must_equal BigDecimal("214748.3646")
      obj.save!
      _(obj.reload.smallmoney).must_equal BigDecimal("214748.3646")
    end

    # Approximate Numerics
    # Float limits are adjusted to 24 or 53 by the database as per http://msdn.microsoft.com/en-us/library/ms173773.aspx
    # Floats with a limit of <= 24 are reduced to reals by sqlserver on creation.

    it "float" do
      col = column("float")
      _(col.sql_type).must_equal "float"
      _(col.type).must_equal :float
      _(col.null).must_equal true
      _(col.default).must_equal 123.00000001
      _(obj.float).must_equal 123.00000001
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Float
      _(type.limit).must_be_nil
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      obj.float = "214748.36461"
      _(obj.float).must_equal 214748.36461
      obj.save!
      _(obj.reload.float).must_equal 214748.36461
    end

    it "real" do
      col = column("real")
      _(col.sql_type).must_equal "real"
      _(col.type).must_equal :real
      _(col.null).must_equal true
      _(col.default).must_be_close_to 123.45, 0.01
      _(obj.real).must_be_close_to 123.45, 0.01
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Real
      _(type.limit).must_be_nil
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      obj.real = "214748.36461"
      _(obj.real).must_be_close_to 214748.36461, 0.01
      obj.save!
      _(obj.reload.real).must_be_close_to 214748.36461, 0.01
    end

    # Date and Time

    it "date" do
      col = column("date")
      _(col.sql_type).must_equal "date"
      _(col.type).must_equal :date
      _(col.null).must_equal true
      _(col.default).must_equal Date.civil(1, 1, 1)
      _(obj.date).must_equal Date.civil(1, 1, 1)
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Date
      _(type.limit).must_be_nil
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Can cast strings. SQL Server format.
      obj.date = "04-01-0001"
      _(obj.date).must_equal Date.civil(1, 4, 1)
      obj.save!
      _(obj.date).must_equal Date.civil(1, 4, 1)
      obj.reload
      _(obj.date).must_equal Date.civil(1, 4, 1)
      # Can cast strings. ISO format.
      obj.date = "0001-04-01"
      _(obj.date).must_equal Date.civil(1, 4, 1)
      obj.save!
      _(obj.date).must_equal Date.civil(1, 4, 1)
      obj.reload
      _(obj.date).must_equal Date.civil(1, 4, 1)
      # Can filter by date range
      _(obj).must_equal obj.class.where(date: obj.date..Date::Infinity.new).first
      # Can keep and return assigned date.
      assert_obj_set_and_save :date, Date.civil(1972, 4, 14)
      # Can accept and cast time objects.
      obj.date = Time.utc(2010, 4, 14, 12, 34, 56, 3000)
      _(obj.date).must_equal Date.civil(2010, 4, 14)
      obj.save!
      _(obj.reload.date).must_equal Date.civil(2010, 4, 14)
    end

    it "datetime" do
      col = column("datetime")
      _(col.sql_type).must_equal "datetime"
      _(col.type).must_equal :datetime
      _(col.null).must_equal true
      time = Time.utc 1753, 1, 1, 0, 0, 0, 123000
      _(col.default).must_equal time, "Microseconds were <#{col.default.usec}> vs <123000>"
      _(obj.datetime).must_equal time, "Microseconds were <#{obj.datetime.usec}> vs <123000>"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::DateTime
      _(type.limit).must_be_nil
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      obj.save!
      _(obj).must_equal obj.class.where(datetime: time).first
      # Can save to proper accuracy and return again.
      time = Time.utc 2010, 4, 1, 12, 34, 56, 3000
      obj.datetime = time
      _(obj.datetime).must_equal time, "Microseconds were <#{obj.datetime.usec}> vs <3000>"
      obj.save!
      _(obj.datetime).must_equal time, "Microseconds were <#{obj.datetime.usec}> vs <3000>"
      obj.reload
      _(obj.datetime).must_equal time, "Microseconds were <#{obj.datetime.usec}> vs <3000>"
      _(obj).must_equal obj.class.where(datetime: time).first
      # Can filter by datetime range
      _(obj).must_equal obj.class.where(datetime: time..DateTime::Infinity.new).first
      # Will cast to true DB value on attribute write, save and return again.
      time = Time.utc 2010, 4, 1, 12, 34, 56, 234567
      time2 = Time.utc 2010, 4, 1, 12, 34, 56, 233000
      obj.datetime = time
      _(obj.datetime).must_equal time2, "Microseconds were <#{obj.datetime.usec}> vs <233000>"
      obj.save!
      _(obj.datetime).must_equal time2, "Microseconds were <#{obj.datetime.usec}> vs <233000>"
      obj.reload
      _(obj.datetime).must_equal time2, "Microseconds were <#{obj.datetime.usec}> vs <233000>"
      _(obj).must_equal obj.class.where(datetime: time).first
      _(obj).must_equal obj.class.where(datetime: time2).first
      # Set and find nil.
      obj.datetime = nil
      _(obj.datetime).must_be_nil
      obj.save!
      _(obj.datetime).must_be_nil
      _(obj).must_equal obj.class.where(datetime: nil).first
    end

    it "datetime2" do
      col = column("datetime2_7")
      _(col.sql_type).must_equal "datetime2(7)"
      _(col.type).must_equal :datetime
      _(col.null).must_equal true
      time = Time.utc 9999, 12, 31, 23, 59, 59, Rational(999999900, 1000)
      _(col.default).must_equal time, "Nanoseconds were <#{col.default.nsec}> vs <999999900>"
      _(obj.datetime2_7).must_equal time, "Nanoseconds were <#{obj.datetime2_7.nsec}> vs <999999900>"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::DateTime2
      _(type.limit).must_be_nil
      _(type.precision).must_equal 7
      _(type.scale).must_be_nil
      obj.save!
      _(obj).must_equal obj.class.where(datetime2_7: time).first
      # Can save 100 nanosecond precisions and return again.
      time = Time.utc 9999, 12, 31, 23, 59, 59, Rational(123456755, 1000)
      time2 = Time.utc 9999, 12, 31, 23, 59, 59, Rational(123456800, 1000)
      obj.datetime2_7 = time
      _(obj.datetime2_7).must_equal time2, "Nanoseconds were <#{obj.datetime2_7.nsec}> vs <123456800>"
      obj.save!
      _(obj.datetime2_7).must_equal time2, "Nanoseconds were <#{obj.datetime2_7.nsec}> vs <123456800>"
      obj.reload
      _(obj.datetime2_7).must_equal time2, "Nanoseconds were <#{obj.datetime2_7.nsec}> vs <123456800>"
      _(obj).must_equal obj.class.where(datetime2_7: time).first
      _(obj).must_equal obj.class.where(datetime2_7: time2).first
      # Can save small fraction nanosecond precisions and return again.
      time = Time.utc 2008, 6, 21, 13, 30, 0, Rational(15020, 1000)
      time2 = Time.utc 2008, 6, 21, 13, 30, 0, Rational(15000, 1000)
      obj.datetime2_7 = time
      _(obj.datetime2_7).must_equal time2, "Nanoseconds were <#{obj.datetime2_7.nsec}> vs <15000>"
      obj.save!
      _(obj.reload.datetime2_7).must_equal time2, "Nanoseconds were <#{obj.datetime2_7.nsec}> vs <15000>"
      _(obj).must_equal obj.class.where(datetime2_7: time).first
      _(obj).must_equal obj.class.where(datetime2_7: time2).first
      # datetime2_3
      time = Time.utc 9999, 12, 31, 23, 59, 59, Rational(123456789, 1000)
      col = column("datetime2_3")
      _(col.fetch_cast_type(connection).precision).must_equal 3
      obj.datetime2_3 = time
      _(obj.datetime2_3).must_equal time.change(nsec: 123000000), "Nanoseconds were <#{obj.datetime2_3.nsec}> vs <123000000>"
      obj.save!
      obj.reload
      _(obj.datetime2_3).must_equal time.change(nsec: 123000000), "Nanoseconds were <#{obj.datetime2_3.nsec}> vs <123000000>"
      _(obj).must_equal obj.class.where(datetime2_3: time).first
      # datetime2_1
      col = column("datetime2_1")
      _(col.fetch_cast_type(connection).precision).must_equal 1
      obj.datetime2_1 = time
      _(obj.datetime2_1).must_equal time.change(nsec: 100000000), "Nanoseconds were <#{obj.datetime2_1.nsec}> vs <100000000>"
      obj.save!
      obj.reload
      _(obj.datetime2_1).must_equal time.change(nsec: 100000000), "Nanoseconds were <#{obj.datetime2_1.nsec}> vs <100000000>"
      _(obj).must_equal obj.class.where(datetime2_1: time).first
      # datetime2_0
      col = column("datetime2_0")
      _(col.fetch_cast_type(connection).precision).must_equal 0
      time = Time.utc 2016, 4, 19, 16, 45, 40, 771036
      obj.datetime2_0 = time
      _(obj.datetime2_0).must_equal time.change(nsec: 0), "Nanoseconds were <#{obj.datetime2_0.nsec}> vs <0>"
      obj.save!
      obj.reload
      _(obj.datetime2_0).must_equal time.change(nsec: 0), "Nanoseconds were <#{obj.datetime2_0.nsec}> vs <0>"
      _(obj).must_equal obj.class.where(datetime2_0: time).first
    end

    it "datetimeoffset" do
      col = column("datetimeoffset_7")
      _(col.sql_type).must_equal "datetimeoffset(7)"
      _(col.type).must_equal :datetimeoffset
      _(col.null).must_equal true
      _(col.default).must_equal Time.new(1984, 1, 24, 4, 20, 0, -28800).change(nsec: 123456700), "Nanoseconds <#{col.default.nsec}> vs <123456700>"
      _(obj.datetimeoffset_7).must_equal Time.new(1984, 1, 24, 4, 20, 0, -28800).change(nsec: 123456700), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <999999900>"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::DateTimeOffset
      _(type.limit).must_be_nil
      _(type.precision).must_equal 7
      _(type.scale).must_be_nil

      # Can save 100 nanosecond precisions and return again.
      obj.datetimeoffset_7 = Time.new(2010, 4, 1, 12, 34, 56, +18000).change(nsec: 123456755)
      _(obj.datetimeoffset_7).must_equal Time.new(2010, 4, 1, 12, 34, 56, +18000).change(nsec: 123456800), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <123456800>"
      obj.save!
      _(obj.datetimeoffset_7).must_equal Time.new(2010, 4, 1, 12, 34, 56, +18000).change(nsec: 123456800), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <123456800>"
      obj.reload
      _(obj.datetimeoffset_7).must_equal Time.new(2010, 4, 1, 12, 34, 56, +18000).change(nsec: 123456800), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <123456800>"

      # Maintains the timezone
      time = ActiveSupport::TimeZone["America/Los_Angeles"].local 2010, 12, 31, 23, 59, 59, Rational(123456800, 1000)
      obj.datetimeoffset_7 = time
      _(obj.datetimeoffset_7).must_equal time
      obj.save!
      _(obj.datetimeoffset_7).must_equal time
      _(obj.reload.datetimeoffset_7).must_equal time

      # With other precisions.
      time = ActiveSupport::TimeZone["America/Los_Angeles"].local 2010, 12, 31, 23, 59, 59, Rational(123456755, 1000)
      col = column("datetimeoffset_3")
      _(col.fetch_cast_type(connection).precision).must_equal 3
      obj.datetimeoffset_3 = time
      _(obj.datetimeoffset_3).must_equal time.change(nsec: 123000000), "Nanoseconds were <#{obj.datetimeoffset_3.nsec}> vs <123000000>"
      obj.save!
      _(obj.datetimeoffset_3).must_equal time.change(nsec: 123000000), "Nanoseconds were <#{obj.datetimeoffset_3.nsec}> vs <123000000>"
      col = column("datetime2_1")
      _(col.fetch_cast_type(connection).precision).must_equal 1
      obj.datetime2_1 = time
      _(obj.datetime2_1).must_equal time.change(nsec: 100000000), "Nanoseconds were <#{obj.datetime2_1.nsec}> vs <100000000>"
      obj.save!
      _(obj.datetime2_1).must_equal time.change(nsec: 100000000), "Nanoseconds were <#{obj.datetime2_1.nsec}> vs <100000000>"
    end

    it "smalldatetime" do
      col = column("smalldatetime")
      _(col.sql_type).must_equal "smalldatetime"
      _(col.type).must_equal :smalldatetime
      _(col.null).must_equal true
      _(col.default).must_equal Time.utc(1901, 1, 1, 15, 45, 0, 0)
      _(obj.smalldatetime).must_equal Time.utc(1901, 1, 1, 15, 45, 0, 0)
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::SmallDateTime
      _(type.limit).must_be_nil
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Will remove fractional seconds and return again.
      obj.smalldatetime = Time.utc(2078, 6, 5, 4, 20, 0, 3000)
      _(obj.smalldatetime).must_equal Time.utc(2078, 6, 5, 4, 20, 0, 0), "Microseconds were <#{obj.smalldatetime.usec}> vs <0>"
      obj.save!
      _(obj.smalldatetime).must_equal Time.utc(2078, 6, 5, 4, 20, 0, 0), "Microseconds were <#{obj.reload.smalldatetime.usec}> vs <0>"
      obj.reload
      _(obj.smalldatetime).must_equal Time.utc(2078, 6, 5, 4, 20, 0, 0), "Microseconds were <#{obj.reload.smalldatetime.usec}> vs <0>"
    end

    it "time(7)" do
      col = column("time_7")
      _(col.sql_type).must_equal "time(7)"
      _(col.type).must_equal :time
      _(col.null).must_equal true
      _(col.default).must_equal Time.utc(1900, 1, 1, 4, 20, 0, Rational(288321500, 1000)), "Nanoseconds were <#{col.default.nsec}> vs <288321500>"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Time
      _(type.limit).must_be_nil
      _(type.precision).must_equal 7
      _(type.scale).must_be_nil
      # Time's #usec precision (low micro)
      obj.time_7 = Time.utc(2000, 1, 1, 15, 45, 0, 300)
      _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Microseconds were <#{obj.time_7.usec}> vs <0>"
      _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Nanoseconds were <#{obj.time_7.nsec}> vs <300>"
      obj.save!
      obj.reload
      _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Microseconds were <#{obj.time_7.usec}> vs <0>"
      _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Nanoseconds were <#{obj.time_7.nsec}> vs <300>"
      # Time's #usec precision (high micro)
      obj.time_7 = Time.utc(2000, 1, 1, 15, 45, 0, 234567)
      _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 234567), "Microseconds were <#{obj.time_7.usec}> vs <234567>"
      obj.save!
      obj.reload
      _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 234567), "Microseconds were <#{obj.time_7.usec}> vs <234567>"
      # Time's #usec precision (high nano rounded)
      obj.time_7 = Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321545, 1000))
      _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_7.nsec}> vs <288321500>"
      obj.save!
      obj.reload
      _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_7.nsec}> vs <288321500>"
    end

    it "time(2)" do
      col = column("time_2")
      _(col.sql_type).must_equal "time(2)"
      _(col.type).must_equal :time
      _(col.null).must_equal true
      _(col.default).must_be_nil
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Time
      _(type.limit).must_be_nil
      _(type.precision).must_equal 2
      _(type.scale).must_be_nil
      # Always uses TinyTDS/Windows 2000-01-01 convention too.
      obj.time_2 = Time.utc(2015, 1, 10, 15, 45, 0, 0)
      _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 0)
      obj.save!
      obj.reload
      _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 0)
      # Time's #usec precision (barely in 2 precision equal to 0.03 seconds)
      obj.time_2 = Time.utc(2000, 1, 1, 15, 45, 0, 30000)
      _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 30000), "Microseconds were <#{obj.time_2.usec}> vs <30000>"
      obj.save!
      obj.reload
      _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 30000), "Microseconds were <#{obj.time_2.usec}> vs <30000>"
      # Time's #usec precision (below 2 precision)
      obj.time_2 = Time.utc(2000, 1, 1, 15, 45, 0, 4000)
      _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 0), "Microseconds were <#{obj.time_2.usec}> vs <0>"
      obj.save!
      obj.reload
      _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 0), "Microseconds were <#{obj.time_2.usec}> vs <0>"
    end

    it "time using default precision" do
      col = column("time_default")
      _(col.sql_type).must_equal "time(7)"
      _(col.type).must_equal :time
      _(col.null).must_equal true
      _(col.default).must_equal Time.utc(1900, 1, 1, 15, 3, 42, Rational(62197800, 1000)), "Nanoseconds were <#{col.default.nsec}> vs <62197800>"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Time
      _(type.limit).must_be_nil
      _(type.precision).must_equal 7
      _(type.scale).must_be_nil
      # Time's #usec precision (low micro)
      obj.time_default = Time.utc(2000, 1, 1, 15, 45, 0, 300)
      _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Microseconds were <#{obj.time_default.usec}> vs <0>"
      _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Nanoseconds were <#{obj.time_default.nsec}> vs <300>"
      obj.save!
      obj.reload
      _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Microseconds were <#{obj.time_default.usec}> vs <0>"
      _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Nanoseconds were <#{obj.time_default.nsec}> vs <300>"
      # Time's #usec precision (high micro)
      obj.time_default = Time.utc(2000, 1, 1, 15, 45, 0, 234567)
      _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 234567), "Microseconds were <#{obj.time_default.usec}> vs <234567>"
      obj.save!
      obj.reload
      _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 234567), "Microseconds were <#{obj.time_default.usec}> vs <234567>"
      # Time's #usec precision (high nano rounded)
      obj.time_default = Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321545, 1000))
      _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_default.nsec}> vs <288321500>"
      obj.save!
      obj.reload
      _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_default.nsec}> vs <288321500>"
    end

    # Character Strings

    it "char(10)" do
      col = column("char_10")
      _(col.sql_type).must_equal "char(10)"
      _(col.type).must_equal :char
      _(col.null).must_equal true
      _(col.default).must_equal "1234567890"
      _(obj.char_10).must_equal "1234567890"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Char
      _(type.limit).must_equal 10
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      obj.char_10 = "012345"
      _(obj.char_10.strip).must_equal "012345"
      obj.save!
      _(obj.reload.char_10.strip).must_equal "012345"
    end

    it "varchar(50)" do
      col = column("varchar_50")
      _(col.sql_type).must_equal "varchar(50)"
      _(col.type).must_equal :varchar
      _(col.null).must_equal true
      _(col.default).must_equal "test varchar_50"
      _(obj.varchar_50).must_equal "test varchar_50"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Varchar
      _(type.limit).must_equal 50
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      assert_obj_set_and_save :varchar_50, "Hello World"
    end

    it "varchar(max)" do
      col = column("varchar_max")
      _(col.sql_type).must_equal "varchar(max)"
      _(col.type).must_equal :varchar_max
      _(col.null).must_equal true
      _(col.default).must_equal "test varchar_max"
      _(obj.varchar_max).must_equal "test varchar_max"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::VarcharMax
      _(type.limit).must_equal 2_147_483_647
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      assert_obj_set_and_save :varchar_max, "Hello World"
    end

    it "text" do
      col = column("text")
      _(col.sql_type).must_equal "text"
      _(col.type).must_equal :text_basic
      _(col.null).must_equal true
      _(col.default).must_equal "test text"
      _(obj.text).must_equal "test text"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Text
      _(type.limit).must_equal 2_147_483_647
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      assert_obj_set_and_save :text, "Hello World"
    end

    # Unicode Character Strings

    it "nchar(10)" do
      col = column("nchar_10")
      _(col.sql_type).must_equal "nchar(10)"
      _(col.type).must_equal :nchar
      _(col.null).must_equal true
      _(col.default).must_equal "12345678åå"
      _(obj.nchar_10).must_equal "12345678åå"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::UnicodeChar
      _(type.limit).must_equal 10
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      obj.nchar_10 = "五六"
      _(obj.nchar_10.strip).must_equal "五六"
      obj.save!
      _(obj.reload.nchar_10.strip).must_equal "五六"
    end

    it "nvarchar(50)" do
      col = column("nvarchar_50")
      _(col.sql_type).must_equal "nvarchar(50)"
      _(col.type).must_equal :string
      _(col.null).must_equal true
      _(col.default).must_equal "test nvarchar_50 åå"
      _(obj.nvarchar_50).must_equal "test nvarchar_50 åå"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::UnicodeVarchar
      _(type.limit).must_equal 50
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      assert_obj_set_and_save :nvarchar_50, "一二34五六"
    end

    it "nvarchar(max)" do
      col = column("nvarchar_max")
      _(col.sql_type).must_equal "nvarchar(max)"
      _(col.type).must_equal :text
      _(col.null).must_equal true
      _(col.default).must_equal "test nvarchar_max åå"
      _(obj.nvarchar_max).must_equal "test nvarchar_max åå"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::UnicodeVarcharMax
      _(type.limit).must_equal 2_147_483_647
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      assert_obj_set_and_save :nvarchar_max, "一二34五六"
    end

    it "ntext" do
      col = column("ntext")
      _(col.sql_type).must_equal "ntext"
      _(col.type).must_equal :ntext
      _(col.null).must_equal true
      _(col.default).must_equal "test ntext åå"
      _(obj.ntext).must_equal "test ntext åå"
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::UnicodeText
      _(type.limit).must_equal 2_147_483_647
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      assert_obj_set_and_save :ntext, "一二34五六"
    end

    # Binary Strings

    let(:binary_file) { File.join ARTest::SQLServer.test_root_sqlserver, "fixtures", "1px.gif" }
    let(:binary_data) { File.binread(binary_file) }

    it "binary(49)" do
      col = column("binary_49")
      _(col.sql_type).must_equal "binary(49)"
      _(col.type).must_equal :binary_basic
      _(col.null).must_equal true
      _(col.default).must_be_nil
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Binary
      _(type.limit).must_equal 49
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      _(binary_data.encoding).must_equal Encoding::BINARY
      _(binary_data.length).must_equal 49
      obj.binary_49 = binary_data
      _(obj.binary_49).must_equal binary_data
      obj.save!
      _(obj.reload.binary_49).must_equal binary_data
    end

    it "varbinary(49)" do
      col = column("varbinary_49")
      _(col.sql_type).must_equal "varbinary(49)"
      _(col.type).must_equal :varbinary
      _(col.null).must_equal true
      _(col.default).must_be_nil
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Varbinary
      _(type.limit).must_equal 49
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      binary_data20 = binary_data.to(20)
      _(binary_data20.encoding).must_equal Encoding::BINARY
      obj.varbinary_49 = binary_data20
      _(obj.varbinary_49).must_equal binary_data20
      obj.save!
      _(obj.reload.varbinary_49).must_equal binary_data20
    end

    it "varbinary(max)" do
      col = column("varbinary_max")
      _(col.sql_type).must_equal "varbinary(max)"
      _(col.type).must_equal :binary
      _(col.null).must_equal true
      _(col.default).must_be_nil
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::VarbinaryMax
      _(type.limit).must_equal 2_147_483_647
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      _(binary_data.encoding).must_equal Encoding::BINARY
      assert_obj_set_and_save :varbinary_max, binary_data
    end

    # Other Data Types

    it "uniqueidentifier" do
      col = column("uniqueidentifier")
      _(col.sql_type).must_equal "uniqueidentifier"
      _(col.type).must_equal :uuid
      _(col.null).must_equal true
      _(col.default).must_be_nil
      _(col.default_function).must_equal "newid()"
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Uuid
      _(type.limit).must_be_nil
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic set and save.
      obj.uniqueidentifier = "this will not qualify as valid"
      _(obj.uniqueidentifier).must_be_nil
      obj.save!
      obj.reload
      _(obj.uniqueidentifier).must_match ActiveRecord::ConnectionAdapters::SQLServer::Type::Uuid::ACCEPTABLE_UUID
      obj.uniqueidentifier = "6F9619FF-8B86-D011-B42D-00C04FC964FF"
      _(obj.uniqueidentifier).must_equal "6F9619FF-8B86-D011-B42D-00C04FC964FF"
      obj.save!
      obj.reload
      _(obj.uniqueidentifier).must_equal "6F9619FF-8B86-D011-B42D-00C04FC964FF"
    end

    it "timestamp" do
      col = column("timestamp")
      _(col.sql_type).must_equal "timestamp"
      _(col.type).must_equal :ss_timestamp
      _(col.null).must_equal true
      _(col.default).must_be_nil
      _(col.default_function).must_be_nil
      type = col.fetch_cast_type(connection)
      _(type).must_be_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Type::Timestamp
      _(type.limit).must_be_nil
      _(type.precision).must_be_nil
      _(type.scale).must_be_nil
      # Basic read.
      _(obj.timestamp).must_be_nil
      obj.save!
      obj.reload
      _(obj.timestamp).must_match %r{\000}
      obj.timestamp
      # Can set another attribute
      obj.uniqueidentifier = "6F9619FF-8B86-D011-B42D-00C04FC964FF"
      obj.save!
    end

    it "does not mark object as changed after save" do
      obj.save!
      obj.attributes
      _(obj.changed?).must_equal false
    end
  end
end
